apollo_l1_gas_price,apollo_l1_gas_price_types: hold Chainlink query failures for the retry interval - #14992
Conversation
09068b0 to
a47b43d
Compare
c50e226 to
c2af008
Compare
a47b43d to
f9da995
Compare
c2af008 to
8fafedf
Compare
f9da995 to
ce31b09
Compare
8fafedf to
c9650d1
Compare
ce31b09 to
2688157
Compare
c9650d1 to
04688be
Compare
2688157 to
e2d17a5
Compare
04688be to
4715b2c
Compare
e2d17a5 to
57731b2
Compare
4715b2c to
1e09207
Compare
57731b2 to
e1e1732
Compare
1e09207 to
40c63d0
Compare
b67ebe4 to
fb3d2ed
Compare
888697e to
3df37d8
Compare
matanl-starkware
left a comment
There was a problem hiding this comment.
@matanl-starkware reviewed 3 files and all commit messages, and made 2 comments.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on asaf-sw).
crates/apollo_l1_gas_price/src/chainlink_oracle/mod.rs line 236 at r1 (raw file):
if let Some(valid_read) = state.last_valid_read { return Ok(valid_read.rate); }
Only within 3*interval timeout. Following PR?
Code quote:
if let Some(valid_read) = state.last_valid_read {
return Ok(valid_read.rate);
}crates/apollo_l1_gas_price/src/chainlink_oracle/mod.rs line 240 at r1 (raw file):
Some(error) => Err(error), None => Err(ExchangeRateOracleClientError::QueryNotReadyError(block_timestamp)), }
Slightly nicer IMO.
Suggestion:
match &state.last_error {
Some(error) => Err(error.clone()),
None => Err(ExchangeRateOracleClientError::QueryNotReadyError(block_timestamp)),
}3df37d8 to
6f93b2f
Compare
fb3d2ed to
162b540
Compare
matanl-starkware
left a comment
There was a problem hiding this comment.
@matanl-starkware made 1 comment and resolved 1 discussion.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on asaf-sw).
crates/apollo_l1_gas_price/src/chainlink_oracle/mod.rs line 236 at r1 (raw file):
Previously, matanl-starkware (Matan Lior) wrote…
Only within 3*interval timeout. Following PR?
Yeh, I see it there. 14993.
162b540 to
6644123
Compare
6f93b2f to
5389c40
Compare
6644123 to
5809dfb
Compare
5389c40 to
0165fea
Compare
|
Previously, matanl-starkware (Matan Lior) wrote…
I agree. |
0165fea to
3140b1c
Compare
asaf-sw
left a comment
There was a problem hiding this comment.
@asaf-sw+AGNT made 1 comment.
Reviewable status: 1 of 3 files reviewed, 1 unresolved discussion (waiting on matanl-starkware).
crates/apollo_l1_gas_price/src/chainlink_oracle/mod.rs line 240 at r1 (raw file):
Previously, asaf-sw wrote…
I agree.
Done, matching on a reference so nothing is cloned when there is no held error.
matanl-starkware
left a comment
There was a problem hiding this comment.
@matanl-starkware reviewed 2 files and all commit messages, and resolved 1 discussion.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on asaf-sw).
5809dfb to
dbe9769
Compare
3140b1c to
ba0f4e0
Compare
dbe9769 to
1c10af6
Compare
ba0f4e0 to
06a158a
Compare
…ailures for the retry interval
06a158a to
881b0ab
Compare
Merge activity
|
asaf-sw
left a comment
There was a problem hiding this comment.
@asaf-sw reviewed 3 files and all commit messages.
Reviewable status: 2 of 3 files reviewed, all discussions resolved (waiting on matanl-starkware).
matanl-starkware
left a comment
There was a problem hiding this comment.
@matanl-starkware reviewed 1 file and all commit messages.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on asaf-sw).
|
Security scan complete — no issues detected. Generated by Claude Code |

Holds a failed Chainlink query's error in
OracleState::last_errorand spawns the next query oncefailure_retry_interval_secondselapses rather than on every call. Without it, everyfetch_rateagainst a broken feed spawns a fresh query, two view calls per feed per proposal, for as long as the feed stays broken.A9b of the split of #14942, on top of the A9a lifecycle.
no_call_is_denied_a_rate_the_client_holdsis the regression test for the availability bug found in review of apollo_l1_gas_price,apollo_l1_gas_price_config,apollo_l1_gas_price_types: add Chainlink client #14942, where the one call that observed a failing query finish was handed that failure without consulting the rate the client already held.Detailed Summary for AI Bots
Stacked on #14991. Part of the L1 price oracle replacement split of #14942.
What
Negative caching for the Chainlink client: a failed query's error is held in
OracleState::last_errorand served to callers with nothing better, and the next query is spawned oncefailure_retry_interval_secondselapses instead of on every call.Why hold a failure
Without it, every
fetch_rateagainst a failing feed spawns a fresh query: two view calls per feed per attempt, on every proposal, for as long as the feed stays broken. A hostile or broken feed would otherwise cost blocking VM executions per block. Holding the failure bounds the retry cadence to one query per retry interval, which is also why the interval is its own config key rather than the sampling interval: a transient failure costs one retry interval, not the rest of the sampling interval.The precedence rule
A held failure is served only when no valid read is held. Bugbot's first review of #14942 caught an availability regression in exactly this spot: the one call that observed a failing query finish was returned that failure directly, without consulting the rate the client already held.
no_call_is_denied_a_rate_the_client_holdsis the regression test, asserting every call through the failure's harvest is served the held rate;a_held_failure_does_not_mask_the_last_valid_ratepins the steady state after the failure is held.A success clears
last_error, so the client never reports a failure older than its newest read.Landing state
metrics().register()runs under apollo_l1_gas_price: register each oracle metric set once #14977'sOnceguard.[Temporary comment]aboveRateKindinapollo_l1_gas_price_typesis deleted, which is why the title carries two scopes: apollo_l1_gas_price,apollo_l1_gas_price_config: ChainlinkOracleClient query lifecycle #14991'sChainlinkRate: RateKindsupertrait bound is the cross-crate justification for itspubthat the marker existed to promise.no_call_is_denied_a_rate_the_client_holds.Testing
4 new tests (82 total). A failing feed is queried once per retry interval, with the batcher call counter flat across ten calls inside the interval; the retry fires exactly at the interval and not one second earlier; a held failure does not mask the last valid rate, including on the very call that harvests the failure.
🤖 Generated with Claude Code